package top.callback.inf.beautyleaf.segment.model;

import lombok.Data;

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 为了减轻数据库的压力，提高发号的效率，将两个号码段 {@link Segment} 组成一个号码段缓冲区。当正在使用的号码段已发号超过
 * 指定比例（默认 10%）时，另一个号码段就会去申请自己要维护的 ID，等当前号码段的 ID 耗尽后直接切换使用。
 *
 * @author Pengyu Gan
 * CreateDate 2021/3/23
 */
@Data
public class SegmentBuffer {

    /**
     * 标签名称，每个标签 {@link LeafTag} 对应唯一一个缓冲区
     */
    private String tagName;

    /**
     * 当前缓冲区维护的两个号码段
     */
    private Segment[] segments;

    /**
     * 当前使用号码段的下标，用于从 segments 中获取号码段
     */
    private volatile int currentSegmentPosition;

    /**
     * 另一个没有使用的号码段是否已准备好可以切换使用（即申请到了 ID）
     */
    private volatile boolean nextSegmentReady;

    /**
     * 缓冲区是否初始化完成（即相关信息已从数据库加载到内存）
     */
    private volatile boolean bufferInitOk;

    /**
     * 另一个号码段申请 ID 所使用的线程是否还在运行，用于判断 ID 是否申请完成
     */
    private final AtomicBoolean loadSegmentThreadRunning;

    /**
     * 缓冲区的读写锁，用于控制状态变化时的线程安全
     */
    private final ReadWriteLock lock;

    /**
     * 下次要申请 ID 时的步长。这个步长会根据发号的速度动态调整，但不会影响到数据库中 step 字段的值。
     */
    private volatile int step;

    /**
     * 申请 ID 的最小步长，等于数据库中配置的 step
     */
    private volatile int minStep;

    /**
     * 上一次申请新号码段的时间，这个时间用于计算发号速度
     */
    private volatile long lastUpdateTimestamp;

    public SegmentBuffer() {
        segments = new Segment[]{new Segment(this), new Segment(this)};
        currentSegmentPosition = 0;
        nextSegmentReady = false;
        bufferInitOk = false;
        loadSegmentThreadRunning = new AtomicBoolean(false);
        lock = new ReentrantReadWriteLock();
    }

    /**
     * 获取当前正在使用的号码段
     * @return 当前正在使用的号码段
     */
    public Segment getCurrent() {
        return segments[currentSegmentPosition];
    }

    /**
     * 计算下一个要使用的号码段的下标
     * @return 下一个要使用的号码段的下标
     */
    public int nextSegmentPosition() {
        return (currentSegmentPosition + 1) % 2;
    }

    /**
     * 切换到要使用号码段的下标
     */
    public void switchSegmentPosition() {
        currentSegmentPosition = nextSegmentPosition();
    }

    /**
     * 获取读锁
     * @return 读锁
     */
    public Lock rLock() {
        return lock.readLock();
    }

    /**
     * 获取写锁
     * @return 写锁
     */
    public Lock wLock() {
        return lock.writeLock();
    }

    @Override
    public String toString() {
        return "SegmentBuffer{" +
                "tagName='" + tagName + '\'' +
                ", segments=" + Arrays.toString(segments) +
                ", currentSegmentPosition=" + currentSegmentPosition +
                ", nextSegmentReady=" + nextSegmentReady +
                ", bufferInitOk=" + bufferInitOk +
                ", loadSegmentThreadRunning=" + loadSegmentThreadRunning +
                ", step=" + step +
                ", minStep=" + minStep +
                ", lastUpdateTimestamp=" + lastUpdateTimestamp +
                '}';
    }
}
